/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dataModel.jsonReader;

import dataModel.jsonIntermediate.*;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import dataModel.jsonStructure.Doc;
import dataModel.jsonStructure.ElementLevel;
import dataModel.jsonStructure.LaneLevel;
import dataModel.jsonStructure.PoolLevel;
import dataModel.process.Activity;
import dataModel.process.ActivityType;
import dataModel.process.Arc;
import dataModel.process.DataObject;
import dataModel.process.DataObjectType;
import dataModel.process.DataStore;
import dataModel.process.Element;
import dataModel.process.Event;
import dataModel.process.EventType;
import dataModel.process.Gateway;
import dataModel.process.GatewayType;
import dataModel.process.Lane;
import dataModel.process.Pool;
import dataModel.process.ProcessModel;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author Bruna
 */
public class JSONReader {

    private ArrayList<String> fileNames;
    private HashMap<String, Integer> keyMap;
    private HashMap<Integer, Integer> shapeMap;
    private ArrayList<JSONTask> tasks;
    private ArrayList<JSONEvent> events;
    private HashMap<Integer, JSONArc> arcs;
    private HashMap<Integer, JSONElem> elems;
    private ArrayList<JSONGateway> gateways;
    private ArrayList<JSONLane> lanes;
    private ArrayList<JSONPool> pools;
    private ArrayList<JSONPool> subProcesses;
    private ArrayList<JSONDataStore> dataStores;
    private ArrayList<JSONDataObject> dataObject;
    private boolean wasCorrect = true;
    private int idCounter;

    public String getJSONStringFromFile(String file) throws IOException {
        BufferedReader fr = new BufferedReader(new FileReader(file));
        String data;
        String json = "";
        while ((data = fr.readLine()) != null) {
            json = json + data;
        }
        return json;
    }

    public void init() {
        fileNames = new ArrayList<String>();
        tasks = new ArrayList<JSONTask>();
        arcs = new HashMap<Integer, JSONArc>();
        gateways = new ArrayList<JSONGateway>();
        events = new ArrayList<JSONEvent>();
        lanes = new ArrayList<JSONLane>();
        keyMap = new HashMap<String, Integer>();
        shapeMap = new HashMap<Integer, Integer>();
        pools = new ArrayList<JSONPool>();
        subProcesses = new ArrayList<JSONPool>();
        elems = new HashMap<Integer, JSONElem>();
        idCounter = 0;
        wasCorrect = true;
        dataStores =  new ArrayList<JSONDataStore>();
        dataObject = new ArrayList<JSONDataObject>();
    }

    private int getId(String rid) {
        int id;
        if (keyMap.containsKey(rid)) {
            id = keyMap.get(rid);
        } else {
            id = idCounter;
            idCounter++;
            keyMap.put(rid, id);
        }
        return id;
    }
    
    private int getId() {
        int id = idCounter;
        idCounter++;
        keyMap.put("newElem" + id, id);
        return id;
    }

    private String cleanString(String s) {
        String temp = s;
        if (temp.contains("glossary://")) {
            temp = temp.replace("glossary://", "");
            temp = temp.substring(temp.indexOf("/") + 1, temp.length());
            temp = temp.replace(";;", "");
        }
        return temp;
    }

    /**
     * Read from JSON
     */
    public void getIntermediateProcessFromFile(Doc doc)
            throws TransformerException, ParserConfigurationException {
        int id = 0;

        int currentLaneId = -1;
        int currentPoolId = -1;

        // Pool level
        for (PoolLevel pool : doc.getChildShapes()) {
            String elemName = pool.getStencil().toString();
            if (elemName.contains(" ")) {
                elemName = elemName.replace(" ", "");
            }

            id = getId(pool.getResourceId());

            // Pool
            if (pool.getStencil().toString().contains("Pool")) {
                currentPoolId = id;
                String temp = cleanString(pool.getProps().getName());
                JSONPool jPool = new JSONPool(id, temp);
                pools.add(jPool);
            }

            // SequenceFlow
            if (pool.getStencil().toString().equals("SequenceFlow")) {
                int targetId = getId(pool.getTarget().getResourceId());
                JSONArc jArc = new JSONArc(id, targetId, currentLaneId, pool.getProps().getName(), "SequenceFlow");
                arcs.put(id, jArc);
            }

            // MessageFlow
            /*if (pool.getStencil().toString().equals("MessageFlow")) {
                JSONArc jArc = new JSONArc(id, shapeMap.get(id), currentLaneId, pool.getProps().getName(), "MessageFlow");
                arcs.put(id, jArc);
            }*/

            //Association_Undirected
            if (pool.getStencil().toString().equals("Association_Undirected")) {
                int targetId = getId(pool.getTarget().getResourceId());
                JSONArc jArc = new JSONArc(id, targetId, currentLaneId, pool.getProps().getName(), "Association_Undirected");
                arcs.put(id, jArc);
            }
            
            //Association_Unidirectional
            if (pool.getStencil().toString().equals("Association_Unidirectional")) {
                int targetId = getId(pool.getTarget().getResourceId());
                JSONArc jArc = new JSONArc(id, targetId, currentLaneId, pool.getProps().getName(), "Association_Unidirectional");
                //JSONArc jArc = new JSONArc(id, shapeMap.get(id), currentLaneId, pool.getProps().getName(), "Association_Unidirectional");
                arcs.put(id, jArc);
            }
            
            // Add lane level
            for (LaneLevel lane : pool.getChildShapes()) {
                id = getId(lane.getResourceId());
                currentLaneId = id;
                String temp = cleanString(lane.getProps().getName());
                JSONLane jlane = new JSONLane(id, temp, currentPoolId);
                lanes.add(jlane);

                // Element level
                for (ElementLevel elem : lane.getChildShapes()) {
                    // Add elements
                    addElems(elem, currentLaneId, currentPoolId);
                }
            }
        }
    }

    private void addElems(ElementLevel elem, Integer currentLaneId, int currentPoolId) {
        int id = getId(elem.getResourceId());

        // Save outgoing elements
        ArrayList<Integer> jArcIDs = new ArrayList<Integer>();
        for (ElementLevel out : elem.getOutgoing()) {
            jArcIDs.add(getId(out.getResourceId()));
        }

        if (elem.getStencil().toString().equals("Task")) {
            JSONTask jTask = new JSONTask(id, elem.getProps().getName(), jArcIDs, currentLaneId, currentPoolId, elem.getProps().getTasktype(),elem.getProps().getLooptype());
            tasks.add(jTask);
            elems.put(id, jTask);
            
            /**/
            //elem.getProps().getTasktype() pega o tipo da atividade (ex: business rule)
            
            /**/
            //Implementar para multipla instancia
        }
        if (elem.getStencil().toString().equals("CollapsedSubprocess")) {
            JSONTask jTask = new JSONTask(id, elem.getProps().getName(), jArcIDs, currentLaneId, currentPoolId, "Subprocess");
            tasks.add(jTask);
            elems.put(id, jTask);
        }

        if (elem.getStencil().toString().toLowerCase().contains("event")) {
            JSONEvent jEvent = new JSONEvent(id, elem.getProps().getName(), elem.getStencil().toString(), currentLaneId, currentPoolId, jArcIDs);
            events.add(jEvent);
            elems.put(id, jEvent);
        }

        if (elem.getStencil().toString().toLowerCase().contains("gateway")) {
            JSONGateway jGateway = new JSONGateway(id, elem.getProps().getName(), jArcIDs, currentLaneId, currentPoolId, elem.getStencil().toString());
            gateways.add(jGateway);
            elems.put(id, jGateway);
        }
        if (elem.getStencil().toString().equals("Subprocess")) {
            subProcesses.add(new JSONPool(id, elem.getProps().getName()));
            for (ElementLevel subElem : elem.getChildShapes()) {
                addElems(subElem, currentLaneId, currentPoolId);
            }
        }
        
        /*CODIGO TESTE*/
        if(elem.getStencil().toString().toLowerCase().contains("datastore")){
            JSONDataStore jDataStore = new JSONDataStore(id, elem.getProps().getName(), jArcIDs, currentLaneId, currentPoolId, elem.getStencil().toString());
            dataStores.add(jDataStore);
            elems.put(id, jDataStore);          
        }
        
        /*Data Object*/
        if(elem.getStencil().toString().toLowerCase().contains("dataobject")){
            JSONDataObject jDataObject = new JSONDataObject(id, elem.getProps().getName(), jArcIDs, currentLaneId, currentPoolId, elem.getStencil().toString());
            dataObject.add(jDataObject);
            elems.put(id, jDataObject);          
        }
    }
    
    private int getEventType(JSONEvent jEvent) {
        try {
            int type = EventType.TYPE_MAP.get(jEvent.getType());
            return type;
        } catch (Exception e) {
            System.out.println("Error: Event Mapping (" + jEvent.getType() + ")");
        }
        return 5;
    }
    
    public ProcessModel getProcessModelFromIntermediate() {
        ProcessModel model = new ProcessModel(-1, "Process Model");
        HashMap<Integer, Element> idMap = new HashMap<Integer, Element>();
        //idMap = new HashMap<Integer, Element>();
        HashMap<Integer, Lane> laneMap = new HashMap<Integer, Lane>();
        HashMap<Integer, Pool> poolMap = new HashMap<Integer, Pool>();

        // Map Pools
        for (JSONPool jPool : pools) {
            Pool pool = new Pool(jPool.getId(), jPool.getLabel());
            model.addPool(jPool.getLabel());
            poolMap.put(jPool.getId(), pool);

            // Map Lanes
            for (JSONLane jLane : lanes) {
                Lane lane = new Lane(jLane.getId(), jLane.getLabel(), pool);
                model.addLane(jLane.getLabel());
                laneMap.put(jLane.getId(), lane);
            }
        }

        // Iterate over all elems to create the according model objects
        for (JSONElem elem : elems.values()) {
            if (elem.getClass().toString().endsWith("JSONTask")) {
                JSONTask jTask = (JSONTask) elem;
                Activity activity = new Activity(jTask.getId(), jTask.getLabel().replaceAll("\n", " "), laneMap.get(jTask.getLaneId()), poolMap.get(jTask.getPoolId()), ActivityType.TYPE_MAP.get(jTask.getType()), jTask.getLooptype());
                
                /**/
                // Null Pointer Exception porque na classe GatewayType não esta implementado atividade tipo Business Rule
                
                model.addActivity(activity);
                idMap.put(jTask.getId(), activity);
            }
            if (elem.getClass().toString().endsWith("JSONEvent")) {
                JSONEvent jEvent = (JSONEvent) elem;
                Event event = new Event(jEvent.getId(), jEvent.getLabel(), laneMap.get(jEvent.getLaneId()), poolMap.get(jEvent.getPoolId()), getEventType(jEvent));
                model.addEvent(event);
                idMap.put(jEvent.getId(), event);
            }
            if (elem.getClass().toString().endsWith("JSONGateway")) {
                JSONGateway jGateway = (JSONGateway) elem;
                Gateway gateway = new Gateway(jGateway.getId(), jGateway.getLabel(), laneMap.get(jGateway.getLaneId()), poolMap.get(jGateway.getPoolId()), GatewayType.TYPE_MAP.get(jGateway.getType()));
                model.addGateway(gateway);
                idMap.put(jGateway.getId(), gateway);
            }
            if (elem.getClass().toString().endsWith("JSONDataStore")) {
                JSONDataStore jDataStore = (JSONDataStore) elem;
                DataStore dataStore = new DataStore(jDataStore.getId(), jDataStore.getLabel(), laneMap.get(jDataStore.getLaneId()), poolMap.get(jDataStore.getPoolId()), DataObjectType.TYPE_MAP.get(jDataStore.getType()));
                model.addDataStore(dataStore);
                idMap.put(jDataStore.getId(), dataStore);
            }
            if (elem.getClass().toString().endsWith("JSONDataObject")) {
                JSONDataObject jDataObject = (JSONDataObject) elem;
                DataObject dataObject = new DataObject(jDataObject.getId(), jDataObject.getLabel(), laneMap.get(jDataObject.getLaneId()), poolMap.get(jDataObject.getPoolId()), DataObjectType.TYPE_MAP.get(jDataObject.getType()));
                model.addDataObject(dataObject);
                idMap.put(jDataObject.getId(), dataObject);
            }
            
        }

        HashMap<Integer, Integer> externalPathInitiators = new HashMap<Integer, Integer>();

        // Iterate over all elems to create the according arcs
        for (JSONElem elem : elems.values()) {
            for (int outId : elem.getArcs()) {

                // if considered outgoing id does not belong to an arc, create a new one (in order to connect attached event)
                if (elems.containsKey(outId)) {
                    Activity activity = ((Activity) idMap.get(elem.getId()));
                    activity.addAttachedEvent(outId);

                    // Attached event leads to alternative path
                    if (elem.getArcs().size() > 1) {
                        System.out.println("Attached Event with alternative Path detected");
                        ((Event) model.getElem(outId)).setIsAttachedTo(elem.getId());
                        ((Event) model.getElem(outId)).setAttached(true);
                        externalPathInitiators.put(outId, elem.getId());

                        // Attached event goes back to standard path	
                    } else {
                        Arc arc = new Arc(getId(), "", idMap.get(elem.getId()), idMap.get(outId), "VirtualFlow");
                        Event attEvent = ((Event) idMap.get(outId));
                        attEvent.setAttached(true);
                        attEvent.setIsAttachedTo(elem.getId());
                        model.addArc(arc);
                    }
                    // Considered outgoing id exists as arc
                } else if (arcs.keySet().contains(outId)) {
                    JSONArc jArc = arcs.get(outId);
                    Arc arc = new Arc(outId, jArc.getLabel(), idMap.get(elem.getId()), idMap.get(jArc.getTarget()), jArc.getType());
                    model.addArc(arc);
                } else {
                    System.out.println("No according Arc found: " + outId);
                }
            }
        }

        // remove all external path initiators
        for (int exPI : externalPathInitiators.keySet()) {
            ProcessModel alternativePath = new ProcessModel(exPI, "");

            // Create start event
            Event startEvent = new Event(getId(), "", model.getElem(exPI).getLane(), model.getElem(exPI).getPool(), EventType.START_EVENT);
            alternativePath.addEvent(startEvent);

            // Reallocate elems to alternative path
            createAlternativePath(exPI, true, model, alternativePath, exPI);

            // Add arc from artifical start to real start elem
            Event realStart = (Event) alternativePath.getElem(exPI);
            alternativePath.addArc(new Arc(getId(), "", startEvent, realStart));

            // Add path to model
            model.addAlternativePath(alternativePath, exPI);
        }

        return model;
    }
   
    private void createAlternativePath(int id, boolean isElem, ProcessModel model, ProcessModel alternative, int exPI) {
        if (isElem) {
            JSONElem elem = elems.get(id);
            if (elem.getArcs().size() > 0) {
                for (int arc : elem.getArcs()) {
                    createAlternativePath(arc, false, model, alternative, exPI);
                    alternative.addElem(model.getElem(id));
                    elems.remove(id);
                    model.removeElem(id);
                    System.out.println("Elem reallocated: " + id + " " + elem.getLabel() + " --> " + exPI);
                }
            } else {
                alternative.addElem(model.getElem(id));
                elems.remove(id);
                model.removeElem(id);
                System.out.println("Elem reallocated: " + id + " " + elem.getLabel() + " --> " + exPI);
            }
        } else {
            createAlternativePath(arcs.get(id).getTarget(), true, model, alternative, exPI);
            alternative.addArc(model.getArc(id));
            arcs.remove(id);
            model.removeArc(id);
            System.out.println("Arc reallocated: " + id + " --> " + exPI);
        }
    }
}
